from PyQt5.QtWidgets import (QMainWindow, QWidget, QVBoxLayout, QHBoxLayout, 
                             QPushButton, QLabel, QTableWidget, QTableWidgetItem,
                             QLineEdit, QTextEdit, QComboBox, QStatusBar, QMessageBox, QInputDialog,
                             QSizePolicy)  # 新增QSizePolicy导入
from PyQt5.QtCore import Qt, QTimer, QThread, pyqtSignal
from PyQt5.QtGui import QImage, QPixmap, QFont, QPainter
import cv2
import sys
from PyQt5.QtWidgets import QApplication
import matplotlib.pyplot as plt
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
# 注意：请确保core和config模块存在
from core import (face_recognition as face_core,
                  gesture_detection as gesture_core,
                  voice_recognition as voice_core,
                  score_management as score_core)
from config.settings import CAMERA_INDEX

# 设置matplotlib中文显示
plt.rcParams['font.sans-serif'] = ['SimHei']  # 黑体
plt.rcParams['axes.unicode_minus'] = False  # 解决负号显示问题

class ScoreChart(FigureCanvas):
    """用matplotlib实现积分趋势图（替代QtCharts）"""
    def __init__(self, parent=None):
        # 创建图形和子图
        self.fig, self.ax = plt.subplots(figsize=(3, 2), dpi=100)
        super().__init__(self.fig)
        self.setParent(parent)
        self.fig.tight_layout()  # 自动调整布局

    def update_chart(self, top_scores):
        """更新积分图表（柱状图）"""
        self.ax.clear()  # 清空原有图表
        names = [name for name, _ in top_scores]
        scores = [score for _, score in top_scores]
        
        # 画柱状图
        bars = self.ax.bar(names, scores, color=['#3498db', '#e74c3c', '#2ecc71', '#f39c12', '#9b59b6'])
        self.ax.set_ylabel('积分')
        self.ax.set_title('积分TOP5', fontsize=10)
        self.ax.tick_params(axis='x', rotation=45)  # X轴文字旋转45度，避免重叠
        
        # 在柱子上显示具体分数
        for bar, score in zip(bars, scores):
            height = bar.get_height()
            self.ax.text(bar.get_x() + bar.get_width()/2., height + 0.1,
                        f'{score}', ha='center', va='bottom', fontsize=8)
        
        self.draw()  # 重新绘制

class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("课堂智能点名+积分管理系统 V1.0")
        self.setGeometry(100, 100, 1200, 800)  # 窗口大小（x,y,宽,高）
        
        # 初始化状态变量
        self.camera_running = False  # 摄像头是否运行
        self.cap = None
        self.timer = QTimer(self)  # 定时器用于更新视频画面
        self.log_text = ""  # 操作日志
        self.orig_video_size = (640, 480)  # 摄像头原始尺寸（宽, 高），与后续设置一致
        
        # 初始化UI组件
        self.init_ui()
        # 加载学生数据和积分
        self.load_student_data()
        
    def init_ui(self):
        """初始化UI布局和组件"""
        # 中心widget（所有组件的容器）
        central_widget = QWidget()
        self.setCentralWidget(central_widget)
        main_layout = QHBoxLayout(central_widget)
        
        # ---------------------- 左侧功能控制区（250px宽）----------------------
        left_layout = QVBoxLayout()
        left_widget = QWidget()
        left_widget.setFixedWidth(250)
        left_widget.setStyleSheet("background-color: #f5f5f5; border-radius: 8px; padding: 10px;")
        
        # 标题
        left_title = QLabel("功能控制")
        left_title.setFont(QFont("SimHei", 14, QFont.Bold))
        left_title.setAlignment(Qt.AlignCenter)
        left_layout.addWidget(left_title)
        left_layout.addSpacing(20)
        
        # 启动/停止按钮
        self.start_btn = QPushButton("启动系统")
        self.start_btn.setFont(QFont("SimHei", 12))
        self.start_btn.setStyleSheet("background-color: #2ecc71; color: white; padding: 10px; border-radius: 4px;")
        self.start_btn.clicked.connect(self.toggle_camera)
        left_layout.addWidget(self.start_btn)
        left_layout.addSpacing(10)
        
        # 手动点名按钮
        self.manual_call_btn = QPushButton("手动点名")
        self.manual_call_btn.setFont(QFont("SimHei", 12))
        self.manual_call_btn.setStyleSheet("background-color: #3498db; color: white; padding: 10px; border-radius: 4px;")
        self.manual_call_btn.clicked.connect(self.manual_call)
        left_layout.addWidget(self.manual_call_btn)
        left_layout.addSpacing(10)
        
        # 手动加减分（输入框+按钮）
        score_layout = QHBoxLayout()
        self.student_name_input = QLineEdit()
        self.student_name_input.setPlaceholderText("输入学生姓名")
        self.student_name_input.setFont(QFont("SimHei", 11))
        score_layout.addWidget(self.student_name_input)
        
        self.score_num_input = QLineEdit()
        self.score_num_input.setPlaceholderText("分数")
        self.score_num_input.setFixedWidth(60)
        self.score_num_input.setFont(QFont("SimHei", 11))
        score_layout.addWidget(self.score_num_input)
        
        self.add_score_btn = QPushButton("加分")
        self.add_score_btn.setStyleSheet("background-color: #e67e22; color: white;")
        self.add_score_btn.clicked.connect(lambda: self.manual_update_score("add"))
        score_layout.addWidget(self.add_score_btn)
        
        self.sub_score_btn = QPushButton("减分")
        self.sub_score_btn.setStyleSheet("background-color: #e74c3c; color: white;")
        self.sub_score_btn.clicked.connect(lambda: self.manual_update_score("subtract"))
        score_layout.addWidget(self.sub_score_btn)
        
        left_layout.addLayout(score_layout)
        left_layout.addSpacing(10)
        
        # 导出Excel按钮
        self.export_btn = QPushButton("导出积分Excel")
        self.export_btn.setFont(QFont("SimHei", 12))
        self.export_btn.setStyleSheet("background-color: #9b59b6; color: white; padding: 10px; border-radius: 4px;")
        self.export_btn.clicked.connect(self.export_excel)
        left_layout.addWidget(self.export_btn)
        left_layout.addSpacing(20)
        
        # 操作日志文本框
        log_label = QLabel("操作日志")
        log_label.setFont(QFont("SimHei", 12, QFont.Bold))
        left_layout.addWidget(log_label)
        
        self.log_textedit = QTextEdit()
        self.log_textedit.setReadOnly(True)
        self.log_textedit.setFont(QFont("SimHei", 10))
        self.log_textedit.setStyleSheet("background-color: white; border: 1px solid #ddd;")
        left_layout.addWidget(self.log_textedit, 1)  # 占满剩余空间
        
        left_widget.setLayout(left_layout)
        main_layout.addWidget(left_widget)
        
        # ---------------------- 中间核心视频区（占满中间）----------------------
        middle_layout = QVBoxLayout()
        middle_widget = QWidget()
        middle_widget.setStyleSheet("background-color: #ffffff; border-radius: 8px; padding: 10px;")
        
        # 视频显示标签 - 关键修改点1：设置尺寸策略和最大尺寸
        self.video_label = QLabel()
        self.video_label.setStyleSheet("border: 2px solid #3498db; border-radius: 4px;")
        self.video_label.setAlignment(Qt.AlignCenter)
        # 设置尺寸策略：优先保持原始大小，避免被布局强制拉伸
        self.video_label.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Preferred)
        # 设置最大尺寸（原始尺寸的2倍，避免无限制放大）
        self.video_label.setMaximumSize(
            self.orig_video_size[0] * 2,  # 最大宽度
            self.orig_video_size[1] * 2   # 最大高度
        )
        middle_layout.addWidget(self.video_label, 1)  # 占满大部分空间
        
        # 识别结果显示
        self.recognition_label = QLabel("等待识别...")
        self.recognition_label.setFont(QFont("SimHei", 24, QFont.Bold))
        self.recognition_label.setAlignment(Qt.AlignCenter)
        self.recognition_label.setStyleSheet("color: #2c3e50; margin-top: 10px;")
        middle_layout.addWidget(self.recognition_label)
        
        middle_widget.setLayout(middle_layout)
        main_layout.addWidget(middle_widget, 1)  # 占满中间空间
        
        # ---------------------- 右侧积分统计区（300px宽）----------------------
        right_layout = QVBoxLayout()
        right_widget = QWidget()
        right_widget.setFixedWidth(300)
        right_widget.setStyleSheet("background-color: #f5f5f5; border-radius: 8px; padding: 10px;")
        
        # 积分排行标题
        right_title = QLabel("积分排行")
        right_title.setFont(QFont("SimHei", 14, QFont.Bold))
        right_title.setAlignment(Qt.AlignCenter)
        right_layout.addWidget(right_title)
        right_layout.addSpacing(10)
        
        # 积分表格
        self.score_table = QTableWidget()
        self.score_table.setColumnCount(2)
        self.score_table.setHorizontalHeaderLabels(["学生姓名", "当前积分"])
        self.score_table.horizontalHeader().setStretchLastSection(True)  # 第二列自适应宽度
        self.score_table.setFont(QFont("SimHei", 11))
        right_layout.addWidget(self.score_table, 1)  # 占满大部分空间
        right_layout.addSpacing(10)
        
        # 积分趋势图（用matplotlib替代QtCharts）
        chart_label = QLabel("积分趋势（前5名）")
        chart_label.setFont(QFont("SimHei", 12, QFont.Bold))
        right_layout.addWidget(chart_label)
        
        self.score_chart = ScoreChart()  # 实例化自定义图表组件
        self.score_chart.setFixedHeight(150)
        right_layout.addWidget(self.score_chart)
        
        right_widget.setLayout(right_layout)
        main_layout.addWidget(right_widget)
        
        # ---------------------- 底部状态栏 ----------------------
        self.status_bar = QStatusBar()
        self.status_bar.setFont(QFont("SimHei", 10))
        self.setStatusBar(self.status_bar)
        self.update_status("未启动 | 摄像头：未连接 | 麦克风：未连接")
        
        # ---------------------- 定时器（更新视频画面）----------------------
        self.timer.timeout.connect(self.update_video_frame)
        
    def load_student_data(self):
        """加载学生人脸库和积分数据"""
        try:
            # 加载人脸库
            student_names = face_core.load_student_face_database()
            # 初始化积分
            score_core.init_scores(student_names)
            # 更新积分表格
            self.update_score_table()
            # 更新积分趋势图
            self.update_score_chart()
            # 日志记录
            self.add_log(f"成功加载{len(student_names)}名学生数据")
            self.update_status(f"就绪 | 摄像头：未连接 | 麦克风：正常")
        except Exception as e:
            QMessageBox.warning(self, "警告", f"加载学生数据失败：{str(e)}")
            self.add_log(f"加载学生数据失败：{str(e)}")
    
    def toggle_camera(self):
        """启动/停止摄像头"""
        if not self.camera_running:
            # 启动摄像头
            self.cap = cv2.VideoCapture(CAMERA_INDEX)
            # 设置摄像头采集尺寸（与初始化的原始尺寸一致）
            self.cap.set(cv2.CAP_PROP_FRAME_WIDTH, self.orig_video_size[0])
            self.cap.set(cv2.CAP_PROP_FRAME_HEIGHT, self.orig_video_size[1])
            if self.cap.isOpened():
                self.camera_running = True
                self.start_btn.setText("停止系统")
                self.start_btn.setStyleSheet("background-color: #e74c3c; color: white; padding: 10px; border-radius: 4px;")
                self.timer.start(30)  # 每30ms更新一帧（约33fps）
                self.add_log("系统启动成功，摄像头已连接")
                self.update_status(f"运行中 | 摄像头：正常 | 麦克风：正常")
            else:
                QMessageBox.warning(self, "警告", "无法打开摄像头，请检查连接")
                self.add_log("摄像头启动失败")
        else:
            # 停止摄像头
            self.camera_running = False
            self.timer.stop()
            self.cap.release()
            self.start_btn.setText("启动系统")
            self.start_btn.setStyleSheet("background-color: #2ecc71; color: white; padding: 10px; border-radius: 4px;")
            self.video_label.clear()
            self.recognition_label.setText("等待识别...")
            self.add_log("系统停止，摄像头已关闭")
            self.update_status("未启动 | 摄像头：未连接 | 麦克风：正常")
    
    def update_video_frame(self):
        """更新视频画面，同时执行手势+人脸检测 - 关键修改点2：优化缩放逻辑"""
        ret, frame = self.cap.read()
        if ret:
            # 镜像翻转
            frame = cv2.flip(frame, 1)
            
            # 手势检测
            hand_detected, frame = gesture_core.detect_gesture(frame)
            
            # 人脸识别
            frame, recognized_name = face_core.recognize_face(frame, hand_detected)
            
            # 更新识别结果显示
            if recognized_name:
                self.recognition_label.setText(f"识别到：{recognized_name}")
                self.add_log(f"点名成功：{recognized_name}")
            
            # 转换为PyQt5支持的格式（BGR→RGB）
            rgb_frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
            h, w, ch = rgb_frame.shape
            bytes_per_line = ch * w
            qt_image = QImage(rgb_frame.data, w, h, bytes_per_line, QImage.Format_RGB888)
            
            # 关键修改：基于原始比例计算合理缩放尺寸，避免随标签大小无限放大
            # 1. 获取视频标签可用空间（去除边框等）
            label_rect = self.video_label.contentsRect()
            label_w, label_h = label_rect.width(), label_rect.height()
            
            # 2. 计算缩放比例（按原始宽高比，取最小比例避免超出标签范围）
            scale_w = label_w / self.orig_video_size[0]
            scale_h = label_h / self.orig_video_size[1]
            scale = min(scale_w, scale_h)
            
            # 3. 计算最终尺寸（不超过标签大小，且保持原始比例）
            target_w = int(self.orig_video_size[0] * scale)
            target_h = int(self.orig_video_size[1] * scale)
            
            # 4. 按计算出的尺寸缩放（不再直接使用label.size()）
            scaled_image = qt_image.scaled(target_w, target_h, Qt.KeepAspectRatio, Qt.SmoothTransformation)
            self.video_label.setPixmap(QPixmap.fromImage(scaled_image))
    
    def manual_call(self):
        """手动点名（弹出输入框）"""
        name, ok = QInputDialog.getText(self, "手动点名", "请输入学生姓名：")
        if ok and name:
            if name in score_core.student_scores:
                self.recognition_label.setText(f"手动点名：{name}")
                face_core.engine.say(f"这位同学是{name}")
                face_core.engine.runAndWait()
                self.add_log(f"手动点名成功：{name}")
            else:
                QMessageBox.warning(self, "警告", f"未找到学生{name}")
                self.add_log(f"手动点名失败：未找到学生{name}")
    
    def manual_update_score(self, action):
        """手动加减分"""
        name = self.student_name_input.text().strip()
        score_str = self.score_num_input.text().strip()
        
        if not name or not score_str:
            QMessageBox.warning(self, "警告", "请输入学生姓名和分数")
            return
        
        try:
            score = int(score_str)
            if score <= 0:
                QMessageBox.warning(self, "警告", "分数必须为正整数")
                return
            
            # 调用积分模块更新
            feedback = score_core.update_score(name, action, score)
            QMessageBox.information(self, "成功", feedback)
            self.add_log(feedback)
            
            # 更新积分表格和趋势图
            self.update_score_table()
            self.update_score_chart()
            
            # 清空输入框
            self.student_name_input.clear()
            self.score_num_input.clear()
        except ValueError:
            QMessageBox.warning(self, "警告", "分数必须为整数")
    
    def update_score_table(self):
        """更新积分表格"""
        self.score_table.setRowCount(0)  # 清空表格
        sorted_scores = score_core.get_top_scores(count=len(score_core.student_scores))
        for row, (name, score) in enumerate(sorted_scores):
            self.score_table.insertRow(row)
            self.score_table.setItem(row, 0, QTableWidgetItem(name))
            self.score_table.setItem(row, 1, QTableWidgetItem(str(score)))
            # 设置表格内容居中
            for col in range(2):
                self.score_table.item(row, col).setTextAlignment(Qt.AlignCenter)
    
    def update_score_chart(self):
        """更新积分趋势图（调用matplotlib图表组件）"""
        top_scores = score_core.get_top_scores(5)  # 获取前5名积分
        self.score_chart.update_chart(top_scores)  # 调用自定义图表的更新方法
    
    def add_log(self, log):
        """添加操作日志"""
        from datetime import datetime
        time_str = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
        self.log_text = f"[{time_str}] {log}\n" + self.log_text
        self.log_textedit.setText(self.log_text)
    
    def update_status(self, status):
        """更新状态栏信息"""
        self.status_bar.showMessage(status)
    
    def export_excel(self):
        """导出积分到Excel"""
        try:
            from openpyxl import Workbook
            from openpyxl.styles import Font, Alignment, PatternFill
            from datetime import datetime
            
            # 创建工作簿
            wb = Workbook()
            ws = wb.active
            ws.title = "学生积分表"
            
            # 表头样式
            header_font = Font(name="SimHei", size=12, bold=True, color="FFFFFF")
            header_fill = PatternFill(start_color="3498DB", end_color="3498DB", fill_type="solid")
            header_alignment = Alignment(horizontal="center", vertical="center")
            
            # 写入表头
            headers = ["排名", "学生姓名", "当前积分", "更新时间"]
            for col, header in enumerate(headers, 1):
                cell = ws.cell(row=1, column=col, value=header)
                cell.font = header_font
                cell.fill = header_fill
                cell.alignment = header_alignment
            
            # 写入积分数据
            sorted_scores = score_core.get_top_scores(count=len(score_core.student_scores))
            for row, (name, score) in enumerate(sorted_scores, 2):
                ws.cell(row=row, column=1, value=row-1)  # 排名
                ws.cell(row=row, column=2, value=name)    # 姓名
                ws.cell(row=row, column=3, value=score)   # 积分
                ws.cell(row=row, column=4, value=datetime.now().strftime("%Y-%m-%d %H:%M:%S"))  # 更新时间
                
                # 数据样式
                for col in range(1, 5):
                    ws.cell(row=row, column=col).alignment = Alignment(horizontal="center")
            
            # 调整列宽
            ws.column_dimensions['A'].width = 8
            ws.column_dimensions['B'].width = 15
            ws.column_dimensions['C'].width = 10
            ws.column_dimensions['D'].width = 20
            
            # 保存文件
            file_name = f"学生积分表_{datetime.now().strftime('%Y%m%d_%H%M%S')}.xlsx"
            wb.save(file_name)
            
            QMessageBox.information(self, "成功", f"Excel导出成功！\n文件路径：{file_name}")
            self.add_log(f"Excel导出成功：{file_name}")
        except ImportError:
            QMessageBox.warning(self, "依赖缺失", "请先安装openpyxl：pip install openpyxl -i https://pypi.tuna.tsinghua.edu.cn/simple/")
        except Exception as e:
            QMessageBox.warning(self, "导出失败", f"Excel导出失败：{str(e)}")
            self.add_log(f"Excel导出失败：{str(e)}")
    
    def closeEvent(self, event):
        """关闭窗口时释放资源"""
        if self.camera_running:
            self.cap.release()  # 释放摄像头
        gesture_core.release_gesture_resource()  # 释放手势检测资源
        face_core.release_face_resource()  # 释放语音播报资源
        self.add_log("系统正常退出，所有资源已释放")
        event.accept()

if __name__ == "__main__":
    app = QApplication(sys.argv)
    app.setFont(QFont("SimHei"))  # 全局中文字体
    window = MainWindow()
    window.show()
    sys.exit(app.exec_())